#include "stdafx.h"

#include "SearchKbd.h"
#include "Theme.h"
#include "Settings.h"
#include "Language.h"
#include "CommonWM.h"

#ifdef _DEBUG
#include "crtdbg.h"
#endif

#define SKK_DEFAULT_ALPHABET TEXT("EN% .,!?^1@#$&*()-+_=;':\"/\\%abc^2%def^3%ghi^4%jkl^5%mno^6%pqrs^7%tuv^8%wxyz^90")

SearchAlphabetMode SearchAlphabetMgr::s_AlphaMode;
SearchAlphabet SearchAlphabetMgr::s_Alpha1;
SearchAlphabet SearchAlphabetMgr::s_Alpha2;

RECT  SearchKbd::s_Rect = {0};
RECT  SearchKbd::s_FullRect = {0};
SearchKbdKeys SearchKbd::s_SearchString[SKK_MAX_STRING_LENGTH] = {skkNone};
int   SearchKbd::s_SearchStringLen = 0;
TCHAR SearchKbd::s_SearchStringChars[SKK_MAX_STRING_LENGTH] = {0};
int   SearchKbd::s_SearchStringCharsLen = 0;
SearchKbd* SearchKbd::s_pThis = NULL;

//
//
// SEARCH ALPHABET
//
//

SearchAlphabet::SearchAlphabet()
  : letters(NULL), keys(NULL), count(0)
{
  ZeroMemory(labels, sizeof(labels));
  ZeroMemory(labelsLen, sizeof(labelsLen));
  id[0] = 0;
}

SearchAlphabet::SearchAlphabet(const TCHAR* szLetters)
  : letters(NULL), keys(NULL), count(0)
{
  id[0] = 0;
  Create(szLetters);
}

SearchAlphabet::~SearchAlphabet()
{
  if (this->letters != NULL)
    delete[] this->letters;
  if (this->keys != NULL)
    delete[] this->keys;
}

bool SearchAlphabet::Create(const TCHAR* szLetters)
{
  if (!szLetters || szLetters[0] == 0)
    return false;

  if (this->letters != NULL)
  {
    delete[] this->letters;
    this->letters = NULL;
  }
  if (this->keys != NULL)
  {
    delete[] this->keys;
    this->keys = NULL;
  }
  ZeroMemory(labels, sizeof(labels));
  ZeroMemory(labelsLen, sizeof(labelsLen));
  ZeroMemory(invisLabels, sizeof(invisLabels));
  id[0] = 0;

  // parse letters
  this->count = 0;
  for (const TCHAR* ltr = szLetters; *ltr != 0; ltr++)
  {
    if (*ltr != SKK_ALPHABET_SEPARATOR)
      this->count++;
  }

  if (this->count == 0)
    return false;

  this->letters = new TCHAR[this->count];
  this->keys = new int[this->count];

  const TCHAR* ltr = szLetters;
  
  // get ID
  for (; *ltr != 0; ltr++)
    if (*ltr == SKK_ALPHABET_SEPARATOR)
    {
      StringCchCopyN(this->id, SKK_ID_TEXT_LENGTH, szLetters, ltr - szLetters);
      ltr++;
      break;
    }

  // get Keys
  int idx = 0;
  int key = skk1;
  bool putToLabel = true;
  for (; *ltr != 0; ltr++)
  {
    if (*ltr != SKK_ALPHABET_SEPARATOR)
    {
      if (*ltr == SKK_ALPHABET_INVISIBLE)
      {
        if (*(ltr+1) == SKK_ALPHABET_INVISIBLE)
          ltr++;
        else
        {
          invisLabels[key] = *(ltr+1);
          putToLabel = false;
          continue;
        }
      }
      this->letters[idx] = _totlower(*ltr);
      this->keys[idx] = SearchAlphabetMgr::FilterToChar((SearchKbdKeys)key);
      if (putToLabel && this->labelsLen[key] < SKK_LABEL_TEXT_LENGTH)
      {
        this->labels[key][this->labelsLen[key]] = *ltr;
        this->labelsLen[key]++;
      }
      idx++;
    }
    else
    {
      putToLabel = true;
      key++;
      if (key > skk9)
        break;
    }
  }

  return true;
}

const TCHAR* SearchAlphabet::GetId() const
{
  return this->id;
}

const TCHAR* SearchAlphabet::GetLabel(SearchKbdKeys key) const
{
  if (key >= skk1 && key <= skk9)
    return this->labels[key];
  else
    return TEXT("");
}

int SearchAlphabet::GetLabelLength(SearchKbdKeys key) const
{
  if (key >= skk1 && key <= skk9)
    return this->labelsLen[key];
  else
    return 0;
}

TCHAR SearchAlphabet::GetInvisLabel(SearchKbdKeys key) const
{
  if (key >= skk1 && key <= skk9)
    return this->invisLabels[key];
  else
    return TEXT('');
}

bool SearchAlphabet::IsEmpty() const
{
  return count == 0;
}

TCHAR SearchAlphabet::LetterToKey(TCHAR l) const
{
  if (Settings::t9SearchByWords && _istspace(l))
    return TEXT(' ');

  l = _totlower(l);
  for (int i = 0; i < this->count; i++)
  {
    if (this->letters[i] == l)
      return this->keys[i];
  }
  return SearchAlphabetMgr::FilterToChar(skkNone);
}

bool SearchAlphabet::StringToKey(const TCHAR* str, TCHAR* intStr, int maxLen) const
{
  if (!str || !intStr || maxLen == 0)
    return false;

  size_t len = min(_tcslen(str), (size_t)maxLen);
  size_t i;
  for (i = 0; i < len; i++)
  {
    intStr[i] = LetterToKey(str[i]);
  }
  intStr[i] = 0;

  return true;
}

int SearchAlphabet::FindFilter(const TCHAR* str, const TCHAR* filter, int filterLen) const
{
  if (!str || !filter)
    return -1;

  int len = (int)_tcslen(str);
  if (len <= 0 || len < filterLen)
    return -1;

  TCHAR* convStr = new TCHAR[len + 1];
  if (!StringToKey(str, convStr, len + 1))
  {
    delete[] convStr;
    return -1;
  }

  int res;

  if (Settings::t9SearchByWords)
  {
    res = FindWord(convStr, filter);
  }
  else
  {
    TCHAR* strPos = _tcsstr(convStr, filter);
    res = (strPos != NULL) ? strPos - convStr : -1;
  }

  delete[] convStr;

  return res;
}

int SearchAlphabet::FindText(const TCHAR* str, const TCHAR* text) const
{
  if (!str || !text)
    return -1;

  size_t len = _tcslen(str);
  if (len <= 0 || len < _tcslen(text))
    return -1;

  int res;
  if (Settings::t9SearchByWords)
  {
    res = FindWord(str, text);
  }
  else
  {
    TCHAR* strPos = _tcsstr(str, text);
    res = (strPos != NULL) ? strPos - str : -1;
  }

  return res;
}

int SearchAlphabet::FindWord(const TCHAR* str, const TCHAR* word) const
{
  if (!str || !word)
    return -1;

  size_t wordLen = _tcslen(word);
  if (wordLen == 0)
    return -1;

  const TCHAR* start = str;
  for(const TCHAR* curr = str;; curr++)
  {
    // find EOS or first non-letter char
    if (*curr == 0 || (!_istalpha(*curr) && !_istdigit(*curr)))
    {
      if (curr - start >= (int)wordLen)
      {
        if (_tcsncmp(start, word, wordLen) == 0)
          return (start - str);
      }
      start = curr + 1;
      if (*curr == 0)
        break;
    }
  }
  return -1;
}

//
//
// SEARCH ALPHABET MGR
//
//

bool SearchAlphabetMgr::Initialize()
{
  s_Alpha1.Create(Language::searchAlphabet);
  if (s_Alpha1.IsEmpty())
    s_Alpha1.Create(SKK_DEFAULT_ALPHABET);
  else
    s_Alpha2.Create(SKK_DEFAULT_ALPHABET);
  
  if (Settings::t9EnglishAsPrimary && !s_Alpha2.IsEmpty())
    s_AlphaMode = amSecondary;
  else
    s_AlphaMode = amPrimary; 

  return true;
}

void SearchAlphabetMgr::Terminate()
{
}

const SearchAlphabet& SearchAlphabetMgr::GetAlphabet()
{
  if (s_AlphaMode == amSecondary)
    return s_Alpha2;
  else
    return s_Alpha1;
}

bool SearchAlphabetMgr::ToggleLayout()
{
  switch(s_AlphaMode)
  {
  case amPrimary:
    if (!s_Alpha2.IsEmpty())
    {
      s_AlphaMode = amSecondary;
      return true;
    }
    break;
  case amSecondary:
    s_AlphaMode = amPrimary;
    return true;
  }
  return false;
}

const SearchAlphabet& SearchAlphabetMgr::GetPrimaryAlphabet()
{
  return s_Alpha1;
}

const SearchAlphabet& SearchAlphabetMgr::GetSecondaryAlphabet()
{
  return s_Alpha2;
}

bool  SearchAlphabetMgr::IsPrimarySelected()
{
  return s_AlphaMode == amPrimary;
}

TCHAR SearchAlphabetMgr::FilterToChar(SearchKbdKeys filter)
{
  if (filter >= skk1 && filter <= skk9)
    return TEXT('1') + (filter - skk1);
  else
    return TEXT(' ');
}

void  SearchAlphabetMgr::FilterToChars(const SearchKbdKeys* filter, int len, TCHAR* filterChars, int maxChars)
{
  int i = 0;
  for (; i < maxChars; i++)
  {
    if (i >= len)
      break;
    filterChars[i] = FilterToChar(filter[i]);
  }
  filterChars[i] = 0;
}

SearchKbdKeys SearchAlphabetMgr::CharToFilter(TCHAR c)
{
  if (c >= TEXT('1') && c <= TEXT('9'))
    return (SearchKbdKeys)(skk1 + (c - TEXT('1')));
  else if (c == TEXT('0'))
    return skkToggleLayout;
  else
    return skkNone;
}

//
//
// SEARCH KBD
//
//

SearchKbd::SearchKbd(HWND hWnd, bool transparent)
  : m_hWnd(hWnd), m_SelKey(skkNone), m_Mode(skmNormal)
{
  if (s_pThis != NULL)
    throw TEXT("Second instance of SearchKbd is forbidden");

  s_pThis = this;

  s_Rect.left = s_Rect.top = 0;
  s_Rect.right = s_Rect.bottom = 1;
  s_FullRect = s_Rect;
  m_BtnWidth = m_BtnHeight = 1;
  m_SearchStrHeight = 0;
  m_IsTransparent = transparent;

  for (int i = 0; i < skkCount; i++)
    m_KeyState[i] = skksNormal;

  MakeToggleLabel();

  for (int i = 0; i < SKK_MAX_STRING_LENGTH; i++)
    s_SearchString[i] = skkNone;
  s_SearchStringLen = 0;

  s_SearchStringChars[0] = 0;
  s_SearchStringCharsLen = 0;
}

SearchKbd::~SearchKbd()
{
}

void SearchKbd::SetRect(const RECT& rc)
{
  s_FullRect.left = rc.left;
  s_FullRect.right = rc.right;
  s_FullRect.top = rc.top;
  s_FullRect.bottom = rc.bottom;

  if (s_FullRect.right == s_FullRect.left)
    s_FullRect.right = s_FullRect.left + 1;
  if (s_FullRect.bottom == s_FullRect.top)
    s_FullRect.bottom = s_FullRect.top + 1;

  s_Rect = s_FullRect;
  SetMode(m_Mode);

  m_BtnWidth = (s_FullRect.right - s_FullRect.left) / SKK_BUTTONS_PER_ROW;
  m_BtnHeight = ((s_FullRect.bottom - s_FullRect.top - Theme::T9TextFontSize - 5) * SKK_BUTTONS_PER_ROW) / skkCount;
  if (m_BtnWidth == 0) m_BtnWidth = 1;
  if (m_BtnHeight == 0) m_BtnHeight = 1;

  m_SearchStrHeight = s_FullRect.bottom - s_FullRect.top - m_BtnHeight * (skkCount / SKK_BUTTONS_PER_ROW);
}

void SearchKbd::ToggleMode()
{
  if (m_Mode == skmNormal)
  {
    SetMode(skmMinimized);
  }
  else
  {
    SetMode(skmNormal);
  }
  ::PostMessage(m_hWnd, NM_SKK_MODE_CHANGED, (WPARAM)m_Mode, (LPARAM)&s_Rect);
  ::InvalidateRect(m_hWnd, &s_FullRect, FALSE);
}

void SearchKbd::SetMode(SearchKbdMode mode)
{
  m_Mode = mode;
  switch(m_Mode)
  {
  case skmNormal:
    s_Rect.top = s_FullRect.top;
    break;
  case skmMinimized:
    s_Rect.top = s_FullRect.bottom - m_SearchStrHeight;
    break;
  }
}
void SearchKbd::Close()
{
  Reset();
  ::PostMessage(m_hWnd, NM_SKK_CLOSE, 0, 0);
}

void SearchKbd::Reset()
{
  s_SearchStringLen = 0;
  s_SearchString[s_SearchStringLen] = skkNone;
  s_SearchStringChars[0] = 0;
  s_SearchStringCharsLen = 0;
  m_SelKey = skkNone;
  SetMode(skmNormal);
}

void SearchKbd::Draw(HDC hdc, const RECT& rcClip)
{
  RECT rc;
  // adjust clipping
  rc.left = max(rcClip.left, s_Rect.left);
  rc.right = min(rcClip.right, s_Rect.right);
  rc.top = max(rcClip.top, s_Rect.top);
  rc.bottom = min(rcClip.bottom, s_Rect.bottom);

  SetBkMode(hdc, TRANSPARENT);
  SetTextColor(hdc, Theme::rgbPopupText);
  ::SelectObject(hdc, Theme::hpenPopupGrid);
  ::SelectObject(hdc, Theme::hbrPopupBackground);

  RECT rcButton = rc;

  // search string
  if (rc.top < s_Rect.top + m_SearchStrHeight)
  {
    SetTextAlign(hdc, TA_LEFT);
    ::SelectObject(hdc, Theme::T9TextFont);
    rcButton.bottom = rcButton.top + m_SearchStrHeight;
    ::Rectangle(hdc, rcButton.left, rcButton.top, rcButton.right, rcButton.bottom);
    rcButton.left += 4;
    rcButton.top += 2;
    rcButton.bottom -= 2;
    rcButton.right -= 4;
    if (s_SearchStringCharsLen > 0)
    {
      ExtTextOut(hdc, rcButton.left, rcButton.top, ETO_CLIPPED, &rcButton,
            s_SearchStringChars, s_SearchStringCharsLen, NULL);
    }
    // restore button on search string if minimized
    if (m_Mode == skmMinimized)
    {
      HGDIOBJ hbrOld = ::SelectObject(hdc, Theme::hbrPopupForeground);
      POINT rectPoints[3];
      rectPoints[0].x = rcButton.right - Theme::T9TextFontSize / 2;
      rectPoints[0].y = rcButton.top + Theme::T9TextFontSize / 3;
      rectPoints[1].x = rcButton.right;
      rectPoints[1].y = rcButton.bottom - Theme::T9TextFontSize / 3;
      rectPoints[2].x = rcButton.right - Theme::T9TextFontSize;
      rectPoints[2].y = rectPoints[1].y;
      ::Polygon(hdc, rectPoints, 3);
      ::SelectObject(hdc, hbrOld);
    }
  }

  if (m_Mode == skmNormal)
  {
    // buttons
    SetTextAlign(hdc, TA_CENTER);
    ::SelectObject(hdc, Theme::T9ButtonsFont);
    for (int i = skk1; i < skkCount; i++)
    {
      GetButtonRect((SearchKbdKeys)i, rcButton);

      // check if in clipping region
      if (rcButton.left >= rc.right || rcButton.right < rc.left ||
          rcButton.top >= rc.bottom || rcButton.bottom < rc.top)
        continue;

      HGDIOBJ hbrOld = NULL;
      if (m_SelKey == i)
      {
        hbrOld = ::SelectObject(hdc, Theme::hbrPopupForeground);
        ::SetTextColor(hdc, Theme::rgbPopupBackground);
      }

      // background
      ::Rectangle(hdc, rcButton.left, rcButton.top, rcButton.right, rcButton.bottom);

      if (i <= skk9)
      {
        // invisible chars label
        TCHAR invLbl[] = { SearchAlphabetMgr::GetAlphabet().GetInvisLabel((SearchKbdKeys)i), 0 };
        UINT prevTA = ::SetTextAlign(hdc, TA_RIGHT);
        COLORREF prevClr = ::SetTextColor(hdc, Theme::rgbPopupGrid);
        ExtTextOut(hdc, rcButton.left + m_BtnWidth - Theme::T9TextFontSize / 4, rcButton.top, 0, NULL,
          invLbl, 1, NULL);
        ::SetTextColor(hdc, prevClr);
        ::SetTextAlign(hdc, prevTA);
      }

      rcButton.top += (m_BtnHeight - Theme::T9ButtonsFontSize) / 2;
      rcButton.bottom = rcButton.top + Theme::T9ButtonsFontSize;

      if (i <= skk9)
      {
        // button label
        ExtTextOut(hdc, rcButton.left + m_BtnWidth/2, rcButton.top, ETO_CLIPPED, &rcButton,
          SearchAlphabetMgr::GetAlphabet().GetLabel((SearchKbdKeys)i),
          SearchAlphabetMgr::GetAlphabet().GetLabelLength((SearchKbdKeys)i), NULL);
      }
      else
      {
        switch (i)
        {
        case skkToggleMode:
          {
            // temporary solution
            if (m_SelKey == i)
            {
              ::SelectObject(hdc, hbrOld);
              hbrOld = NULL;
            }
            else
            {
              hbrOld = ::SelectObject(hdc, Theme::hbrPopupForeground);
            }

		        POINT rectPoints[3];
		        rectPoints[0].x = rcButton.left + m_BtnWidth / 2;
            rectPoints[0].y = rcButton.top + Theme::T9ButtonsFontSize - Theme::T9ButtonsFontSize / 6;
		        rectPoints[1].x = rcButton.left + m_BtnWidth / 2 - Theme::T9ButtonsFontSize / 2;
		        rectPoints[1].y = rcButton.top + Theme::T9ButtonsFontSize / 6;
		        rectPoints[2].x = rcButton.left + m_BtnWidth / 2 + Theme::T9ButtonsFontSize / 2;
		        rectPoints[2].y = rectPoints[1].y;
            ::Polygon(hdc, rectPoints, 3);
          }
          break;
        case skkToggleLayout:
          if (m_ToggleLabelLen > 0)
          {
            ExtTextOut(hdc, rcButton.left + m_BtnWidth/2, rcButton.top, ETO_CLIPPED, &rcButton,
              m_ToggleLabel, m_ToggleLabelLen, NULL);
          }
          break;
        case skkBackspace:
          {
            // temporary solution
            if (m_SelKey == i)
            {
              ::SelectObject(hdc, hbrOld);
              hbrOld = NULL;
            }
            else
            {
              hbrOld = ::SelectObject(hdc, Theme::hbrPopupForeground);
            }

		        POINT rectPoints[7];
		        rectPoints[0].x = rcButton.left + m_BtnWidth / 2 - Theme::T9ButtonsFontSize / 2;
            rectPoints[0].y = rcButton.top + Theme::T9ButtonsFontSize / 2;
		        rectPoints[1].x = rcButton.left + m_BtnWidth / 2;
		        rectPoints[1].y = rcButton.top + Theme::T9ButtonsFontSize / 6;
		        rectPoints[2].x = rectPoints[1].x;
		        rectPoints[2].y = rcButton.top + Theme::T9ButtonsFontSize / 3;
            rectPoints[3].x = rcButton.left + m_BtnWidth / 2 + Theme::T9ButtonsFontSize / 2;
		        rectPoints[3].y = rectPoints[2].y;
		        rectPoints[4].x = rectPoints[3].x;
		        rectPoints[4].y = rcButton.bottom - Theme::T9ButtonsFontSize / 3;
		        rectPoints[5].x = rectPoints[2].x;
		        rectPoints[5].y = rectPoints[4].y;
		        rectPoints[6].x = rectPoints[1].x;
		        rectPoints[6].y = rcButton.bottom - Theme::T9ButtonsFontSize / 6;
            ::Polygon(hdc, rectPoints, 7);
          }
          break;
        }
      }

      if (hbrOld != NULL)
        ::SelectObject(hdc, hbrOld);

      if (m_SelKey == i)
      {
        SetTextColor(hdc, Theme::rgbPopupText);
      }
    }
  } // if m_Mode == skkNormal
}

void SearchKbd::OnMouseDown(int x, int y)
{
  m_SkipMouseAction = false;
  x -= s_Rect.left;
  y -= s_Rect.top;
  SearchKbdKeys key = PointToButton(x, y);
  SelectButton(key);

  SetTimer(m_hWnd, IDT_TIMER_SEARCHKBD_LAYOUT, SKK_TIMER_LAYOUT_DURATION, SearchKbd::TimerProc);
}

void SearchKbd::OnMouseMove(int x, int y)
{
  if (m_SkipMouseAction)
    return;

  x -= s_Rect.left;
  y -= s_Rect.top;
  SearchKbdKeys key = PointToButton(x, y);

  if (key != m_SelKey)
    KillTimer(m_hWnd, IDT_TIMER_SEARCHKBD_LAYOUT);

  SelectButton(key);
}

void SearchKbd::OnMouseUp(int x, int y)
{
  KillTimer(m_hWnd, IDT_TIMER_SEARCHKBD_LAYOUT);

  if (!m_SkipMouseAction)
  {
    x -= s_Rect.left;
    y -= s_Rect.top;
    if (m_Mode == skmNormal)
    {
      SearchKbdKeys key = PointToButton(x, y);
      if (key != skkNone)
      {
        AddKey(key);
      }
    }
    else
    {
      ToggleMode();
    }
  }
  SelectButton(skkNone);
}

void SearchKbd::AddKey(SearchKbdKeys key)
{
  if (key >= skk1 && key <= skk9)
  {
    if (s_SearchStringLen < SKK_MAX_STRING_LENGTH)
    {
      s_SearchString[s_SearchStringLen] = key;
      s_SearchStringLen++;
      RECT rc = s_Rect;
      rc.bottom = rc.top + m_SearchStrHeight;
      ::PostMessage(m_hWnd, NM_SKK_STRING_UPDATED, (WPARAM)&s_SearchString, (LPARAM)s_SearchStringLen);
      ::InvalidateRect(m_hWnd, &rc, FALSE);
    }
  }
  else
  {
    switch(key)
    {
    case skkToggleMode:
      if (s_SearchStringLen == 0)
        Close();
      else
        ToggleMode();
      return;
    case skkToggleLayout:
      if (SearchAlphabetMgr::ToggleLayout())
      { // request full redraw
        RECT rc = s_Rect;
        rc.top += m_SearchStrHeight;
        ::InvalidateRect(m_hWnd, &rc, FALSE);
        MakeToggleLabel();
      }
      break;
    case skkBackspace:
      if (s_SearchStringLen > 0)
      {
        s_SearchStringLen--;
        s_SearchString[s_SearchStringLen] = skkNone;
        RECT rc = s_Rect;
        rc.bottom = rc.top + m_SearchStrHeight;
        ::InvalidateRect(m_hWnd, &rc, FALSE);
        ::PostMessage(m_hWnd, NM_SKK_STRING_UPDATED, (WPARAM)&s_SearchString, (LPARAM)s_SearchStringLen);
      }
      else
      {
        Close();
      }
      break;
    }
  }
}

bool SearchKbd::IsPtInRect(int x, int y) const
{
  if (x >= s_Rect.left && x <= s_Rect.right &&
      y >= s_Rect.top && y <= s_Rect.bottom)
    return true;
  else
    return false;
}

void SearchKbd::SetDisplayString(const TCHAR str[])
{
  StringCchCopy(s_SearchStringChars, SKK_MAX_STRING_LENGTH, str);
  s_SearchStringCharsLen = _tcslen(s_SearchStringChars);
}

SearchKbdKeys SearchKbd::PointToButton(int x, int y)
{
  int btn = x / m_BtnWidth + ((y - m_SearchStrHeight) / m_BtnHeight) * SKK_BUTTONS_PER_ROW;
  if (btn < skk1 || btn >= skkCount)
    return skkNone;
  else
    return (SearchKbdKeys)btn;
}

void SearchKbd::GetButtonRect(SearchKbdKeys key, RECT& rc)
{
  rc.left = s_Rect.left + (key % SKK_BUTTONS_PER_ROW) * m_BtnWidth;
  rc.right = rc.left + m_BtnWidth;
  rc.top = s_Rect.top + m_SearchStrHeight + (key / SKK_BUTTONS_PER_ROW) * m_BtnHeight;
  rc.bottom = rc.top + m_BtnHeight;
}

void SearchKbd::SelectButton(SearchKbdKeys key)
{
  if (key == m_SelKey)
    return;

  RECT rc;

  if (m_SelKey != skkNone)
  { // redraw previously selected key
    GetButtonRect(m_SelKey, rc);
    ::InvalidateRect(m_hWnd, &rc, FALSE);
  }
  if (m_KeyState[key] & skksDisabled)
    m_SelKey = skkNone;
  else
    m_SelKey = key;
  if (m_SelKey != skkNone)
  { // redraw newly selected key
    GetButtonRect(m_SelKey, rc);
    ::InvalidateRect(m_hWnd, &rc, FALSE);
  }
}

void SearchKbd::MakeToggleLabel()
{
  if (SearchAlphabetMgr::GetSecondaryAlphabet().IsEmpty())
  {
    StringCchCopy(m_ToggleLabel, SKK_LABEL_TEXT_LENGTH, 
      SearchAlphabetMgr::GetPrimaryAlphabet().GetId());
    m_KeyState[skkToggleLayout] = skksDisabled;
  }
  else if (SearchAlphabetMgr::IsPrimarySelected())
  {
    StringCchPrintf(m_ToggleLabel, SKK_LABEL_TEXT_LENGTH, TEXT("%s/%s"), 
      SearchAlphabetMgr::GetPrimaryAlphabet().GetId(),
      SearchAlphabetMgr::GetSecondaryAlphabet().GetId());
    m_KeyState[skkToggleLayout] = skksNormal;
  }
  else
  {
    StringCchPrintf(m_ToggleLabel, SKK_LABEL_TEXT_LENGTH, TEXT("%s/%s"), 
      SearchAlphabetMgr::GetSecondaryAlphabet().GetId(),
      SearchAlphabetMgr::GetPrimaryAlphabet().GetId());
    m_KeyState[skkToggleLayout] = skksNormal;
  }
  m_ToggleLabelLen = _tcslen(m_ToggleLabel);
}

VOID CALLBACK SearchKbd::TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
  if (!SearchKbd::s_pThis)
    return;

  if (idEvent == IDT_TIMER_SEARCHKBD_LAYOUT)
  {
    KillTimer(hwnd, idEvent);
    if (SearchKbd::s_pThis->m_SelKey == skkBackspace)
    {
      SearchKbd::s_pThis->Close();
    }
    else if (SearchAlphabetMgr::ToggleLayout())
    { // request full redraw
      ::InvalidateRect(hwnd, &s_Rect, FALSE);
      SearchKbd::s_pThis->MakeToggleLabel();
    }
    SearchKbd::s_pThis->m_SkipMouseAction = true;
  }
}

WNDPROC ImeEditBox::s_PrevWndProc = 0;

ImeEditBox::ImeEditBox(int nId, HWND hwndParent, HINSTANCE hInst, bool show)
{
  m_hWnd = ::CreateWindowEx(ES_EX_CLEARONBACKPRESSHOLD, TEXT("Edit"), NULL, 
          WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, 
          0, 0, 1, 1, 
          hwndParent, (HMENU)nId, hInst, 0);
  if (!m_hWnd)
    return;

  //::SetWindowLong(m_hWnd, GWL_USERDATA, (LONG)this);
  s_PrevWndProc = (WNDPROC)::SetWindowLong(m_hWnd, GWL_WNDPROC, (LONG)ImeEditBox::WndProc);
  ::SendMessage(m_hWnd, WM_SETFONT, (WPARAM)Theme::SecondaryListFont, 0);

  if (show)
    Show();
}

ImeEditBox::~ImeEditBox()
{
  if (m_hWnd != NULL)
    ::DestroyWindow(m_hWnd);
}

void ImeEditBox::SetRect(const RECT& rc, bool redraw)
{
  if (m_hWnd == NULL)
    return;

  ::MoveWindow(m_hWnd, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, redraw);
}

LRESULT ImeEditBox::DoCtlColorEdit(HDC hdc)
{
  if (m_hWnd == NULL)
    return 0;

  ::SetBkMode(hdc, TRANSPARENT);
  ::SetTextColor(hdc, Theme::rgbListItemSecondaryText);
  return (LRESULT)Theme::hbrListBackground;
}

void ImeEditBox::DoBackspace()
{
  if (m_hWnd == NULL)
    return;

  TCHAR szText[SKK_MAX_STRING_LENGTH];
  int len = ::GetWindowText(m_hWnd, szText, SKK_MAX_STRING_LENGTH);
  if (len > 0)
  {
    szText[len - 1] = 0;
    ::SetWindowText(m_hWnd, szText);
    ::SendMessage(m_hWnd, EM_SETSEL, len-1, -1);
  }
}

void ImeEditBox::SetText(const TCHAR* szText)
{
  if (m_hWnd == NULL)
    return;

  ::SetWindowText(m_hWnd, szText);
}

bool ImeEditBox::GetText(TCHAR* szText, int nSize)
{
  if (m_hWnd == NULL)
    return false;

  return (::GetWindowText(m_hWnd, szText, nSize) != 0);
}

int  ImeEditBox::GetTextLength()
{
  if (m_hWnd == NULL)
    return 0;

  return ::GetWindowTextLength(m_hWnd);
}

void ImeEditBox::Show(int vKey, bool setFocus)
{
  if (m_hWnd == NULL)
    return;
  ::SetWindowText(m_hWnd, TEXT(""));
  ::ShowWindow(m_hWnd, SW_SHOW);
  SetFocus();
  if (vKey != 0)
  {
    keybd_event(vKey, 0, KEYEVENTF_SILENT, 0);
    keybd_event(vKey, 0, KEYEVENTF_KEYUP | KEYEVENTF_SILENT, 0);   
  }
}

void ImeEditBox::Hide()
{
  if (m_hWnd == NULL)
    return;
  ::ShowWindow(m_hWnd, SW_HIDE);
}

bool ImeEditBox::IsVisible()
{
  if (m_hWnd == NULL)
    return false;
  return ::IsWindowVisible(m_hWnd) == TRUE;
}

void ImeEditBox::SetFocus()
{
  if (m_hWnd == NULL)
    return;
  ::SetFocus(m_hWnd);
}

LRESULT CALLBACK ImeEditBox::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  //ImeEditBox* imeEditBox = (ImeEditBox*)::GetWindowLong(m_hWnd, GWL_USERDATA);

  switch(uMsg)
  {
  case WM_KEYDOWN:
  case WM_KEYUP:
    switch (wParam)
    { // redirect these to parent window
    case VK_UP:
    case VK_DOWN:
    case VK_TACTION:
    case VK_TBACK:
      // toggle focus
      //::SetFocus(::GetParent(hWnd));
      ::PostMessage(::GetParent(hWnd), uMsg, wParam, lParam);
      return 0;
    }
    break;
  }
  return ::CallWindowProc(s_PrevWndProc, hWnd, uMsg, wParam, lParam);
}
